package com.tender.shiro;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.tender.dto.ShiroUserDTO;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.tender.constants.Constants;
import javax.servlet.Filter;
import com.tender.shiro.filter.KickedOutAuthorizationFilter;
import org.springframework.context.annotation.DependsOn;
import com.tender.shiro.jwt.ShiroSessionManager;
import com.tender.shiro.filter.*;
import com.tender.jwt.JwtTokenManager;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * shiro 的配置类
 */
@Configuration
public class ShiroConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.password}")
    private String password;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private JwtTokenManager jwtTokenManager;

    private Map<String, Filter> filters() {
        Map<String, Filter> map = new HashMap<>();
        // KickedOutAuthorizationFilter 不能交由 spring 容器管理，切记！
        // 具体理由请参考最上面的文献
        map.put("kickedOut", new KickedOutAuthorizationFilter(redissonClient, sessionDAO(), shiroSessionManager()));
        map.put("jwt-authc", new JwtAuthenticationFilter(jwtTokenManager));
        map.put("jwt-perms", new JwtPermissionsAuthorizationFilter());
        map.put("jwt-roles", new JwtRolesAuthorizationFilter());

        return map;
    }

    /**
     * 创建ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        // 使自定义拦截器生效
        shiroFilterFactoryBean.setFilters(filters());

        // 添加Shiro内置过滤器
        /**
         * Shiro内置过滤器，可以实现权限相关的拦截器
         *    常用的过滤器：
         *       anon:  无需认证（登录）可以访问
         *       authc: 必须认证才可以访问
         *       user:  如果使用rememberMe的功能可以直接访问
         *       perms：该资源必须得到资源权限才可以访问
         *       role:  该资源必须得到角色权限才可以访问
         */
        Map<String, String> filterMap = new LinkedHashMap<String, String>();

        filterMap.put("/hello", "anon");
        filterMap.put("/secret/decrypt", "anon");
        filterMap.put("/secret/decrypt/valid", "anon");
        filterMap.put("/login", "anon");
        filterMap.put("/login4jwt", "anon");
        filterMap.put("/unauthorized", "anon");
        filterMap.put("/logout", "anon");
        filterMap.put("/register/user", "anon");
        // 剩下的都需要认证通过，而且同一个账号要满足同时在线人数的限制，kickedOut 是一个自定义的过滤器
        // filterMap.put("/**", "authc,kickedOut");
        filterMap.put("/**", "jwt-authc,kickedOut");

        // 要求登陆时的链接，非必须。
        shiroFilterFactoryBean.setLoginUrl("/login");
        // shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 创建DefaultWebSecurityManager
     */
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(ShiroCustomRealm shiroCustomRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 关联realm
        securityManager.setRealm(shiroCustomRealm);
        // 关联sessionManager
        securityManager.setSessionManager(shiroSessionManager());
        // 配置缓存的话，退出登录、跳转、认证的时候 crazycake 会报错，要求放在 session 里面的实体类必须有个id标识
        /**
         * @see ShiroUserDTO  这里面 ShiroUserDTO 必须要有 id 属性
         */
        securityManager.setCacheManager(redisCacheManager());
        return securityManager;
    }

    /**
     * 创建Realm
     */
    @Bean
    public ShiroCustomRealm shiroCustomRealm() {
        return new ShiroCustomRealm();
    }

    /**
     * 配置ShiroDialect，用于 thymeleaf 和 shiro 标签配合使用
     */
    @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }

    /**
     * 创建cookie对象，如果不指定这个对象，则cookie的key=value 类似为：JSESSIONID=0BFDF5A54FED31E033F59FF88EA2E21C
     */
    @Bean
    public SimpleCookie simpleCookie() {
        SimpleCookie simpleCookie = new SimpleCookie();
        simpleCookie.setName(Constants.COOKIE_NAME);
        simpleCookie.setHttpOnly(true);
        // path为 / 用于多个系统共享JSESSIONID
        simpleCookie.setPath("/");
        return simpleCookie;
    }

    @Bean
    public DefaultWebSessionManager shiroSessionManager() {
        //DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        ShiroSessionManager sessionManager = new ShiroSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(false);
        sessionManager.setSessionIdCookieEnabled(true);
        // 关联上面定义的 cookie
        sessionManager.setSessionIdCookie(simpleCookie());
        // 删除无效session
        sessionManager.setDeleteInvalidSessions(true);
        // 设置sessionDAO
        sessionManager.setSessionDAO(sessionDAO());
        // session 过期时间
        sessionManager.setGlobalSessionTimeout(Constants.GLOBAL_SESSION_TIMEOUT);
        return sessionManager;
    }

    /**
     * 配置 LifecycleBeanPostProcessor，可以来自动的调用配置在Spring IOC容器中 Shiro Bean 的生命周期方法
     */
    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 开启Shiro注解
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();     // crazycake 实现
        // RedisClusterManager redisClusterManager = new RedisClusterManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setPassword(password);
        redisManager.setTimeout(1800000);
        return redisManager;
    }

    @Bean
    public JavaUuidSessionIdGenerator sessionIdGenerator() {
        return new JavaUuidSessionIdGenerator();
    }

    @Bean
    public RedisSessionDAO sessionDAO() {
        RedisSessionDAO sessionDAO = new RedisSessionDAO(); // crazycake 实现
        sessionDAO.setRedisManager(redisManager());
        sessionDAO.setSessionIdGenerator(sessionIdGenerator()); //  Session ID 生成器
        return sessionDAO;
    }

    @Bean
    public RedisCacheManager redisCacheManager() {
        RedisCacheManager cacheManager = new RedisCacheManager();   // crazycake 实现
        cacheManager.setRedisManager(redisManager());
        return cacheManager;
    }

}
